import os
import shutil
# 执行文件操作
import tkinter as tk
from tkinter import filedialog, messagebox, PhotoImage, simpledialog
import json
# 存储任务
import hashlib
# 解析哈希值，用于判断文件是否相同
import time
# 时间相关
import threading
# 线程相关
from datetime import datetime
#获取时间

class FileSyncApp:
    def __init__(self, master):
        # 构造函数实现构造窗口
        self.master = master
        self.master.title("文件同步工具")

        self.master.geometry("800x600")  # 设置窗口的初始大小
        self.master.config(bg="#f4f4f4")  # 设置背景颜色
    
        self.task_file = "tasks.json"
        self.running = False  # 控制线程运行的标志
        self.stop_event = threading.Event()
        self.root = root
        self.tasks = self.load_tasks()
        # 将二者同步

        # 创建框架以管理布局
        self.main_frame = tk.Frame(master, bg="#f4f4f4")
        self.main_frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        # 源文件夹部分
        self.source_frame = tk.Frame(self.main_frame, bg="#f4f4f4")
        self.source_frame.pack(fill="x", pady=5)
        self.source_label = tk.Label(self.source_frame, text="选择源文件夹:", font=("Arial", 12), bg="#f4f4f4")
        self.source_label.pack(side="left", padx=10)
        self.source_button = tk.Button(self.source_frame, text="选择文件夹", command=self.select_source, font=("Arial", 10), width=20)
        self.source_button.pack(side="left", padx=10)

        # 目标文件夹部分
        self.target_frame = tk.Frame(self.main_frame, bg="#f4f4f4")
        self.target_frame.pack(fill="x", pady=5)
        self.target_label = tk.Label(self.target_frame, text="选择目标文件夹:", font=("Arial", 12), bg="#f4f4f4")
        self.target_label.pack(side="left", padx=10)
        self.target_button = tk.Button(self.target_frame, text="选择文件夹", command=self.select_target, font=("Arial", 10), width=20)
        self.target_button.pack(side="left", padx=10)

        # 保存任务按钮
        self.save_task_button = tk.Button(self.main_frame, text="保存任务", command=self.save_task, font=("Arial", 12), width=20)
        self.save_task_button.pack(side="left", pady=10)

        # 删除任务按钮
        self.save_task_button = tk.Button(self.main_frame, text="删除任务", command=self.delete_task, font=("Arial", 12), width=20)
        self.save_task_button.pack(side="left", pady=10)

        # 执行同步按钮
        self.execute_button = tk.Button(self.main_frame, text="执行同步", command=self.execute_sync, font=("Arial", 12), width=20)
        self.execute_button.pack(pady=10)

        # 执行后台同步按钮
        self.execute_button = tk.Button(self.main_frame, text="后台实时同步", command=self.min_execute, font=("Arial", 12), width=20)
        self.execute_button.pack(pady=10)

        # 关闭后台同步按钮
        self.execute_button = tk.Button(self.main_frame, text="取消实时同步", command=self.execute_end,
                                        font=("Arial", 12), width=20)
        self.execute_button.pack(pady=10)

        # 任务列表部分
        self.task_listbox = tk.Listbox(self.main_frame, height=8, font=("Arial", 10), width=40, selectmode="single")
        self.task_listbox.pack(pady=10)
        self.task_listbox.bind('<<ListboxSelect>>', self.load_selected_task)

        # 创建一个Text框用于显示输出
        self.text_box = tk.Text(root, height=100, width=500)
        self.text_box.pack(padx=10, pady=10)
        self.text_box.config(state=tk.DISABLED)   # 直接设置为只读模式

        # 加载已有任务
        self.load_tasks_into_listbox()

    def load_tasks(self):
        if os.path.exists(self.task_file):
            with open(self.task_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        return []

    def save_tasks(self):
        # 将新生成的任务存储进json中
        with open(self.task_file, 'w', encoding='utf-8') as f:
            json.dump(self.tasks, f)
            # 打开json文件后将数据存在其后

    def select_source(self):
        self.source_folder = filedialog.askdirectory()
        # 弹出对话框让使用者选择相应的文件夹
        self.source_label.config(text=f"源文件夹: {self.source_folder}")

    def select_target(self):
        self.target_folder = filedialog.askdirectory()
        self.target_label.config(text=f"目标文件夹: {self.target_folder}")

    def save_task(self):  # 存储相应的任务
        task_name = f"Task {len(self.tasks) + 1}"  # 返回任务的长度
        self.tasks.append({
            "name": task_name,
            "source": self.source_folder,
            "target": self.target_folder
        })
        self.save_tasks()
        self.load_tasks_into_listbox()
        messagebox.showinfo("任务保存", f"任务 '{task_name}' 已保存")

    def delete_task(self):
        # 获取选中的任务索引
        selected_task_index = self.task_listbox.curselection()

        if not selected_task_index:  # 如果没有选择任务
            messagebox.showwarning("删除任务", "请先选择一个任务")
            return

        # 获取选中的任务
        selected_task_index = selected_task_index[0]
        task_to_delete = self.tasks[selected_task_index]

        # 确认是否删除任务
        if messagebox.askyesno("确认删除", f"确定要删除任务 '{task_to_delete['name']}' 吗？"):
            # 删除任务
            del self.tasks[selected_task_index]

            # 保存更新后的任务列表
            self.save_tasks()

            # 更新任务列表框
            self.load_tasks_into_listbox()

            # 显示提示消息
            messagebox.showinfo("任务删除", f"任务 '{task_to_delete['name']}' 已删除")
        else:
            messagebox.showinfo("删除任务", "删除操作已取消")

    def load_tasks_into_listbox(self):
        self.task_listbox.delete(0, tk.END)
        # 删除表中的任务
        for task in self.tasks:
            # 将json中的任务添加到表中展示
            self.task_listbox.insert(tk.END, task["name"])
            # 每次添加在结尾

    def load_selected_task(self, event):
        # 选择某任务时展示某任务并存储相关信息
        selected_index = self.task_listbox.curselection()
        # 返回当前的项目索引
        if selected_index:
            selected_task = self.tasks[selected_index[0]]
            # 选中的目标任务
            self.source_folder = selected_task['source']
            self.target_folder = selected_task['target']
            # 更新界面显示
            self.source_label.config(text=f"源文件夹: {self.source_folder}")
            self.target_label.config(text=f"目标文件夹: {self.target_folder}")

    def execute_sync(self):
        if not hasattr(self, 'source_folder') or not hasattr(self, 'target_folder'):
            messagebox.showerror("错误", "请先选择源文件夹和目标文件夹")
            return

        current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        
        
        place = os.path.join(self.source_folder)
        c = place.split('/')
        current = c[-1]
        # 创建目标文件夹路径（在原有目标文件夹下，以当前时间为名称的子文件夹）
        current_time = current+current_time
        target_folder_with_time = os.path.join(self.target_folder, current_time)

        # 确保该文件夹存在
        os.makedirs(target_folder_with_time, exist_ok=True)

        for root, dirs, files in os.walk(self.source_folder):
            # 遍历和文件
            for file in files:

                src_file_path = os.path.join(root, file)
                # 将root和file拼成一个完整的路径，并且输出

                rel_path = os.path.relpath(src_file_path, self.source_folder)
                # 计算二者的相对路径
                target_file_path = os.path.join(target_folder_with_time, rel_path)

                # 创建目标文件夹，确保如果存在对应文件时不会错误
                os.makedirs(os.path.dirname(target_file_path), exist_ok=True)

                # 同步文件
                if not os.path.exists(target_file_path) or \
                        not self.are_files_identical(src_file_path, target_file_path):
                    # 检查文件是否存在
                    # 检查是否有相同的文件，如果二者不相同即同步
                    shutil.copy2(src_file_path, target_file_path)
                    # 用于复制文件并保留文件的元数据（如修改时间、创建时间等）
                    self.text_box.config(state=tk.NORMAL)  # 允许修改内容
                    self.text_box.insert(tk.END, f"同步: {src_file_path} 到 {target_file_path}\n")
                    self.text_box.config(state=tk.DISABLED)  # 禁止修改内容
                    self.text_box.update()   # 强制更新文本框

        # 删除目标中不存在的文件
      
        for root, dirs, files in os.walk(self.target_folder):
            for file in files:
                target_file_path = os.path.join(root, file)
                # 将root和file拼成一个完整的路径，并且输出
                rel_path = os.path.relpath(target_file_path, self.target_folder)
                # 计算二者的相对路径
                src_file_path = os.path.join(target_folder_with_time, rel_path)
                # 拼出原路径

                if not os.path.exists(src_file_path):
                    # 检查路径是否存在
                    os.remove(target_file_path)
                    self.text_box.config(state=tk.NORMAL)  # 允许修改内容
                    self.text_box.insert(tk.END, f"删除: {target_file_path}\n")
                    self.text_box.config(state=tk.DISABLED)  # 禁止修改内容
                    self.text_box.update()  # 强制更新文本框

        # messagebox.showinfo("同步完成", "文件同步已完成")
      
        self.text_box.config(state=tk.NORMAL)  # 允许修改内容
        self.text_box.insert(tk.END, "同步完成\n")
        self.text_box.config(state=tk.DISABLED)  # 禁止修改内容
        self.text_box.update()  # 强制更新文本框

    # 判断两个文件是否相同，可以通过文件大小和哈希值来比较
    def are_files_identical(self, src_file, target_file):
        # 首先比较文件大小
        if os.path.getsize(src_file) != os.path.getsize(target_file):
            return False

        # 如果文件大小相同，再比较文件的内容，通过计算文件的哈希值来比较
        src_hash = self.calculate_file_hash(src_file)
        target_hash = self.calculate_file_hash(target_file)

        return src_hash == target_hash

    # 计算文件的哈希值（这里使用MD5算法）
    def calculate_file_hash(self, file_path):
        hash_md5 = hashlib.md5()
        with open(file_path, 'rb') as f:
            # 分块读取文件，避免内存占用过高
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()

    def sync_task(self, time_per):
        while self.running:
            # 在GUI中更新消息
            self.update_gui("同步进行中...")
            # 执行同步操作
            self.execute_sync()
            # 等待指定的时间间隔
            self.stop_event.wait(time_per)

    def update_gui(self, message):
        # 确保更新在主线程中进行
        if self.root.winfo_viewable():
            self.root.after(0, lambda: (
                self.text_box.config(state=tk.NORMAL),
                self.text_box.insert(tk.END, message + "\n"),
                self.text_box.config(state=tk.DISABLED),
                self.text_box.see(tk.END)
            ))

    def execute_end(self):
        """结束后台同步任务"""
        self.running = False
        self.stop_event.set()  # 停止线程等待
        self.update_gui("后台操作结束")

    def min_execute(self):
        user_input = simpledialog.askstring("后台运行时请勿关闭窗口", "请输入同步间隔秒数:")
        if user_input:
            try:
                # 获取用户输入的时间间隔
                time_per = float(user_input)
                if time_per <= 0:
                    messagebox.showinfo("无效输入", "请输入一个大于0的数字。")
                    return

                # 确保没有重复启动线程
                if self.running:
                    messagebox.showinfo("提示", "同步任务已在运行中。")
                    return

                self.running = True
                self.stop_event.clear()  # 重置停止事件

                # 创建并启动后台同步线程
                thread = threading.Thread(target=self.sync_task, args=(time_per,))
                thread.daemon = True  # 设置为守护线程，主程序结束时线程自动退出
                thread.start()

            except ValueError:
                messagebox.showinfo("无效输入", "请输入有效的数字。")


if __name__ == "__main__":
    root = tk.Tk()
    app = FileSyncApp(root)
    root.mainloop()
    # 循环运行程序
